<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;


class BaseModel extends Model
{

    protected static $_db = null;
    protected static $_transactions = 0;

    protected $_now = null;
    protected $_errcode = 0;
    protected $_errmsg = '';

    private $_tableSql = '';
    private $_whereSql = ' 1 ';
    private $_fields = '';
    private $_params = [];
    private $_orderbySql = '';
    private $_limitSql = '';
    private $_lockSql = '';
    private $_page = 0;
    private $_pageSize = 10;

    private $_withJson = false;
    private $_bstat = false;
    private $_stat_fields = '';

    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);
        self::$_db = $this->getDBInstance();

        $this->_now = date('Y-m-d H:i:s');
    }

    public function setError($code,$message = ''){
        $this->_errcode = $code;
        $this->_errmsg = $message;
    }

    public function getError(){
        return ['errcode'=>$this->_errcode,'errmsg'=>$this->_errmsg];
    }

    public static function getDBInstance(){
        return self::$_db ? : DB::connection()->getPdo();
    }

    //db 获取数据快捷方法
    public static function fetch($sql, $params = [], $fetch_style = \PDO::FETCH_ASSOC){
        $stmt = self::getDBInstance()->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetch($fetch_style);
    }
    //db 获取数据快捷方法
    public static function fetchAll($sql, $params = [], $fetch_style = \PDO::FETCH_ASSOC){
        $stmt = self::getDBInstance()->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll($fetch_style);
    }
    //
    public static function execute($sql, $params = []){
        $stmt = self::getDBInstance()->prepare($sql);
        return $stmt->execute($params);
    }

    public static function exec($sql){
        return self::getDBInstance()->exec($sql);
    }

    public static function beginTransaction(){
        ++self::$_transactions;
        if (self::$_transactions == 1) {
            self::getDBInstance()->beginTransaction();
            //self::getDBInstance()->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
        }
    }

    public static function rollBack(){
        if (self::$_transactions == 1) self::getDBInstance()->rollBack();
        --self::$_transactions;
    }

    public static function commit()
    {
        if (self::$_transactions == 1) self::getDBInstance()->commit();
        --self::$_transactions;
    }

    protected function _page($page, $pageSize)
    {
        $this->_page = $page < 1 ? 1 : intval($page);
        $this->_pageSize = $pageSize;
        $offset = ($page - 1) * $pageSize;
        //limit 0
        $this->_limitSql = $pageSize == 0 ? "" : " limit {$offset},{$pageSize} ";
        return $this;
    }

    protected function _tables($tables,$params = []){
        //$this->_clear();
        foreach ($tables as $k=>$v){
            $this->_tableSql .= " {$v}";
        }
        $this->_params = array_merge($this->_params,$params);
        return $this;
    }

    protected function _clear(){
        $this->_fields = '';
        $this->_tableSql = '';
        $this->_whereSql = ' 1 ';
        $this->_params = [];
        $this->_orderbySql = '';
        $this->_limitSql = '';
        $this->_lockSql = '';
        return $this;
    }

    protected static function _merge($data , $key, $soptions = []){
        $options = [];
        switch ($key){
            case 'get':
                $options = [
                    '_specific' => false,
                    '_bstat' => false,
                    '_page' => 1,
                    '_pageSize' => 0,
                    '_orderby' => '',
                    '_lock' => '',
                    '_with_json'=>false,
                    '_with_deleted'=>false
                ];
                break;
            case 'save':
                $options = [
                    '_with_return_info' => false,
                ];
        }

        return array_merge($options,$soptions,$data);
    }

    protected function _lock($sql){
        $this->_lockSql = $sql;
        return $this;
    }

    protected function _where($rules, $data, $extra = []){

        $whereStr = '';
        $params = [];
        foreach ($data as $k=>$v){
            if(isset($rules[$k]) === false || $v === '_all') continue;

            if(is_numeric($v) === false && empty($v) && !array_get($rules[$k],'force'))   continue;

            switch ($rules[$k]['op']){
                case '='://EQ
                case '>'://GT
                case '<'://LT
                case '<>'://NEQ
                case '<='://ELT
                case '>='://EGT
                    $whereStr .= " and {$rules[$k]['sql']}{$rules[$k]['op']}?";
                    $params[] = $v;
                    break;
                case 'is':
                    $whereStr .= $rules[$k][$v ? 'true' : 'false'];
                    break;
                case 'llike':
                case 'rlike':
                    break;
                case 'like':
                    $whereStr .= " and {$rules[$k]['sql']} like ?";
                    $params[] = "%{$v}%";
                    break;
                case 'time_start_at':
                case 'time_end_at':
                    break;
                case 'date_start_at':
                    $whereStr .= " and {$rules[$k]['sql']} >= ?";
                    $params[] = "{$v} 00:00:00";
                    break;
                case 'date_end_at':
                    $whereStr .= " and {$rules[$k]['sql']} <= ?";
                    $params[] = "{$v} 23:59:59";
                    break;
                case 'in':
                    if($v == [])    $whereStr .= " and 0";
                    else{
                        $instr = implode(',', array_fill(0, count($v), '?'));
                        $whereStr .= " and {$rules[$k]['sql']} in ({$instr})";
                        $params = array_merge($params,$v);
                    }
                    break;
                case 'not_in':
                    if(count($v) > 0){
                        $instr = implode(',', array_fill(0, count($v), '?'));
                        $whereStr .= " and {$rules[$k]['sql']} not in ({$instr})";
                        $params = array_merge($params,$v);
                    }
                    break;
                case 'function':
                    $r = call_user_func($rules[$k]['func'],$k,$v);
                    $whereStr .= $r['sql'];
                    $params = array_merge($params,$r['params']);
                default :
                    break;
            }
        }
        $this->_whereSql .= $whereStr;
        $this->_params = array_merge($this->_params,$params);

        if($extra){
            $this->_whereSql .= $extra['sql'];
            $this->_params = array_merge($this->_params,$extra['params']);
        }
        return $this;
    }

    protected function _orderby($sql)
    {
        $this->_orderbySql = $sql;
        return $this;
    }

    protected function _fields($fields)
    {
        $this->_fields = $fields;
        return $this;
    }

    protected function _stat($enable = true, $fields = ' count(*) as total ')
    {
        $this->_bstat = $enable;
        $this->_stat_fields = $fields;
        return $this;
    }

    protected function _getData($options = [])
    {
        $options = array_merge(['_with_json'=>false,'_with_append'=>null],$options);
        $sql = "select {$this->_fields} from {$this->_tableSql} where {$this->_whereSql} {$this->_orderbySql} {$this->_limitSql} {$this->_lockSql}";
        Log::debug(['sql'=>$sql,'params'=>$this->_params]);
        $list = self::fetchAll($sql,$this->_params);

        $bloop = $options['_with_json'] || $options['_with_append'];
        if($bloop){
            foreach ($list as $k => $v){
                if($options['_with_json']){
                    $jarr = $v['json'] ? json_decode($v['json'],true) : [];
                    unset($list[$k]['json']);
                    $list[$k] = array_merge($list[$k],$jarr);
                }

                if($options['_with_append']){
                    $narr = call_user_func($options['_with_append'],$list[$k],$options);
                    $rows[$k] = array_merge($list[$k],$narr);
                }
            }
        }
        if ($this->_bstat) {
            $sql = "select {$this->_stat_fields} from {$this->_tableSql} where {$this->_whereSql}";
            $stat = self::fetch($sql,$this->_params);

            return [
                'list' => $list,
                'stat' => $this->_pageSize == 0 ? $stat : array_merge($stat,['page'=>$this->_page,'pageSize'=>$this->_pageSize])
            ];
        }
        return $list;
    }

    protected function _sort($data){

        $data = array_merge([
            'table'=>'',
            'where'=>'',
        ],$data);
        if($data['table'] == ''){
            $this->_message = 'param error';
            return false;
        }
        self::beginTransaction();
        $group = [];
        for ($i = 0; $i < $len = count($data['list']); $i++){
            $group[] = $data['list'][$i];
            if($i == $len - 1 || $data['list'][$i]['next_id'] != $data['list'][$i+1]['cur_id']){
                $maxid = (int)$group[0]['pre_id'];
                $minid = (int)$group[(count($group)-1)]['next_id'];
                $maxPriority = $minPriority = 0;
                if($minid > 0){
                    $sql = "select * from {$data['table']} where 1 {$data['where']} and id={$minid}";
                    $row = self::fetch($sql);
                    $minPriority = $row['priority'];
                }
                if($maxid > 0){
                    $sql = "select * from {$data['table']} where 1 {$data['where']} and id={$maxid}";
                    $row = self::fetch($sql);
                    $maxPriority = $row['priority'];
                }
                if($minid > 0 && $maxid == 0){
                    $sql = "select * from {$data['table']} where 1 {$data['where']} and priority>{$minPriority} order by priority asc limit 1";
                    $row = self::fetch($sql);
                    if($row){
                        $maxPriority = $row['priority'];
                        $maxid = $row['id'];
                    }
                }
                if($maxid > 0 && $minid == 0){
                    $sql = "select * from {$data['table']} where 1 {$data['where']} and priority<{$maxPriority} order by priority desc limit 1";
                    $row = self::fetch($sql);
                    if($row){
                        $minPriority = $row['priority'];
                        $minid = $row['id'];
                    }
                }
                $glen = count($group);
                if($maxPriority && $maxPriority - $minPriority - 1 < $glen){//
                    $gap = 10;
                    $num = ($glen + 1) * $gap;
                    $sql = "update {$data['table']} set priority=priority+{$num} where 1 {$data['where']} and priority>={$maxPriority} and id<>{$minid}";
                    if(self::execute($sql) === false){
                        self::rollBack();
                        $this->_message = 'db error';
                        return false;
                    }
                }else if($minPriority && $maxPriority){
                    $gap = intval(($maxPriority - $minPriority)/($glen + 1));
                }else {
                    $gap = 10;
                }
                $priority = $minPriority;
                for ($j = $glen - 1; $j >= 0; $j--){
                    $priority += $gap;
                    $sql = "update {$data['table']} set priority={$priority} where 1 {$data['where']} and id={$group[$j]['cur_id']}";
                    if(self::execute($sql) === false){
                        self::rollBack();
                        $this->_message = 'db error';
                        return false;
                    }
                }
                $group = [];
            }
        }
        self::commit();
        return true;
    }

}
